package com.springboot.jdk.concurrency;

/**
 * 双重检测单例模式
 *
 * @author: zr
 * @date: 2021-09-23
 */
public class DoubleCheckSingleton {

    private static DoubleCheckSingleton instance;

    private static Object object;

    public DoubleCheckSingleton() {
        object = new Object();
    }

    // 懂volatil底层原理的话，就知道，多线程共享一个资源的话，首先第一个问题
    // 就是一个 共享变量 的可见性问题

    public DoubleCheckSingleton getInstance() {
        // 第二个线程过来
        // 判断 instance 是否为空，
        // 因为发生了指令重排，instance 对象 虽然不为空，即 instance 对象 已经指向了 分配好的 DoubleCheckSingleton 的地址
        // 但是 instance 对象还没有初始化完成，socket 还没有初始化，直接使用里面的 参数socket 可能会报空指针异常

        // 如果 instance 加了 volatile 关键字，就不会出现上述 对象 不为空，但是对象还没有初始化完成的情况
        // 此时 直接 使用 instance 里面的 属性 肯定是没有任何问题的。
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                // 有一个线程先进来， 返现是null，就进行创建instance,
                // 并且强制将 instance 的值刷入到 主内存中去，并且过期掉其他线程本地缓存中 instance 的值。
                // synchronized 保证原子性 和 可见性

                // 第一个线程出去之后，第二个线程就进来了，
                // 自己本地缓存里面的instance 的值 过期了，可以去主内存读取，

                if (instance == null) {
                    // 创建了一个单例
                    // 创建一个 实例 会分为 3 步走
                    // 1、为 DoubleCheckSingleton 对象 分配内存空间
                    // 2、初始化 DoubleCheckSingleton 对象
                    // 3、让 instance 引用 指向 创建好的 DoubleCheckSingleton

                    // 假设 instance 没有加 volatile 关键字，可能会发生指令重排
                    // 正常的步骤应该是1 -> 2 -> 3, 但是发生指令重排之后，顺序就变成了 1-> 3 -> 2，
                    // 即 instance 变量还没有完成初始化，即 socket 还没有初始化好，就结束了

                    // 加锁 instance 加了 volatile 关键字之后，就不会发生指令重排
                    // 会等到 DoubleCheckSingleton 对象初始化完成之后，才会让 instance 引用指向 已经初始化好的 DoubleCheckSingleton 对象
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}
